#include "ADUFlow.h"


// --------- Custom Shaders -----------------------------------------------
#define STRINGIFY(A)  #A

static const GLchar* _particleShaderVert = STRINGIFY(
attribute vec4 a_position;
uniform float u_size;

void main()
{
    gl_Position = CC_PMatrix * CC_MVMatrix * a_position;
    gl_PointSize = CC_MVMatrix[0][0] * u_size * 2.8;
}
);

// Fragment shader
static const GLchar* _particleShaderFrag = STRINGIFY(

#ifdef GL_ES
precision lowp float;
#endif

void main()
{
    gl_FragColor = texture2D(CC_Texture0, gl_PointCoord);
}
);

//
// --------- Custom Shaders ---------
//           Render Texture
//

// Vertex shader
static const GLchar* _renderTextureShaderVert = STRINGIFY(
attribute vec4 a_position;
attribute vec2 a_texCoord;
attribute vec4 a_color;

#ifdef GL_ES
varying lowp vec4 v_fragmentColor;
varying mediump vec2 v_texCoord;
#else
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
#endif

void main()
{
    gl_Position = CC_PMatrix * a_position;
    v_fragmentColor = a_color;
    v_texCoord = a_texCoord;
}
);

// Fragment shader
static const GLchar* _renderTextureShaderFrag = STRINGIFY(

#ifdef GL_ES
precision lowp float;
#endif

varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform float u_threshold_discard;
uniform float u_threshold_border;

void main()
{
	    //    gl_FragColor = v_fragmentColor * texture2D(CC_Texture0, v_texCoord);
    vec4 color = v_fragmentColor * texture2D(CC_Texture0, v_texCoord);
    if( color.r < u_threshold_discard)
        // black or discard
        color = vec4(0,0,0,0);
    else if( color.r < u_threshold_border)
        // blue for the border
        color = vec4(0,0,1,1);
    else
        // white for the center
        color = vec4(0,0,1,0.8);
    gl_FragColor = color;
});

// ---------End Custom Shaders -----------------------------------------------



Flow::Flow(Vec2& _position) : mLifetimeRandomizer(PARTICLE_LIFETIME_MIN, PARTICLE_LIFETIME_MAX)
{
	mWorld = GameManager::getInstance()->mWorld;
	mPosition = _position;
	initPhysics();
}

Flow::~Flow() {

}

void Flow::draw(Renderer *renderer, const Mat4 &transform, uint32_t transformFlags)
{
    mRenderTexture->beginWithClear(0,0,0,0);
    mCustomCommand.init(_globalZOrder);
    mCustomCommand.func = CC_CALLBACK_0(Flow::onDraw, this, transform, transformFlags);
    renderer->addCommand(&mCustomCommand);
    mRenderTexture->end();
}

void Flow::onDraw(const Mat4 &transform, uint32_t transformFlags)
{
    // transform everything to PTM_RATIO
    Mat4 newMV;
    Mat4::multiply(_modelViewTransform, mRatioTransform, &newMV);
    _glProgramState->apply(newMV);
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)

    GL::bindTexture2D(mTexture->getName());
    GL::blendFunc(mBlendFunc.src, mBlendFunc.dst);
    int totalParticles = mParticleSystem->GetParticleCount();
    // Update Positions
    glDrawArrays(GL_POINTS, 0, totalParticles);
    CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1,totalParticles);
    CHECK_GL_ERROR_DEBUG();
#endif

}

// Blend Interface

/// blendFunc getter
const BlendFunc& Flow::getBlendFunc() const
{
    return mBlendFunc;
}
/// blendFunc setter
void Flow::setBlendFunc(const BlendFunc &var)
{
    mBlendFunc = var;
}

bool Flow::initShader(b2ParticleSystem* particleSystem, float ratio)
{
    mParticleSystem = particleSystem;
    mRatioTransform.scale(ratio, ratio, 1);
    mRatio = ratio;

    // own shader

    auto glprogram = GLProgram::createWithByteArrays(_particleShaderVert, _particleShaderFrag);

    GLProgramState *state = GLProgramState::getOrCreateWithGLProgram(glprogram);
    setGLProgramState(state);

    setup();

    auto textureCache = Director::getInstance()->getTextureCache();
    mTexture = textureCache->addImage(IMG_WATER);

    mBlendFunc = BlendFunc::ALPHA_NON_PREMULTIPLIED;
//    mBlendFunc = BlendFunc::ADDITIVE;


    // create a render texture, this is what we are going to draw into
    auto s = Director::getInstance()->getWinSize();
    mRenderTexture = cocos2d::RenderTexture::create(s.width, s.height, Texture2D::PixelFormat::RGBA8888);
    this->addChild(mRenderTexture);
    mRenderTexture->setAnchorPoint(Point::ANCHOR_MIDDLE);
    mRenderTexture->setPosition(Point(s.width/2, s.height/2));

    // Change RenderTexture shader
    auto program = GLProgram::createWithByteArrays(_renderTextureShaderVert, _renderTextureShaderFrag);
    auto programState = GLProgramState::getOrCreateWithGLProgram(program);
    programState->setUniformFloat("u_threshold_discard", 0.15);
    programState->setUniformFloat("u_threshold_border", 0.2);

    mRenderTexture->getSprite()->setGLProgramState(programState);

    return true;
}

void Flow::setup()
{
    auto glProgramState = getGLProgramState();
    float size = mParticleSystem->GetRadius()*2;

    // Update Positions
    glProgramState->setVertexAttribPointer("a_position", 2, GL_FLOAT, GL_FALSE, 0, mParticleSystem->GetPositionBuffer());
    glProgramState->setUniformFloat("u_size", size);

    CHECK_GL_ERROR_DEBUG();
}


void Flow::	initPhysics() {
	auto s = Director::getInstance()->getWinSize();
	mWorld = GameManager::getInstance()->mWorld;
//	mParticleSystem->SetRadius(0.035f);
//	mParticleSystem->SetMaxParticleCount(MAX_PARTICLE);
//	mParticleSystem->SetDestructionByAge(true);
//	createFlow();
	createWall();
	createFaucet();
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
	initShader(mParticleSystem,PTM_RATIO);
#endif
}

void Flow::createFlow() {
	// particles
    b2ParticleSystemDef particleSystemDef;
    particleSystemDef.dampingStrength = 0.2f;
    particleSystemDef.radius = 0.7f;
    mParticleSystem = mWorld->CreateParticleSystem(&particleSystemDef);
    mParticleSystem->SetGravityScale(0.4f);
    mParticleSystem->SetDensity(1.2f);

    b2ParticleGroupDef pd;
    pd.flags = b2_waterParticle;

    b2PolygonShape shape2;
    shape2.SetAsBox(9.0f, 9.0f, b2Vec2(0.0f, 0.0f), 0.0);
    
    pd.shape = &shape2;
    mParticleSystem->CreateParticleGroup(pd);
}

void Flow::createFaucet() {
	b2ParticleSystemDef particleSystemDef;
	mParticleSystem = mWorld->CreateParticleSystem(&particleSystemDef);
	mParticleSystem->SetRadius(RADIUS_PARTICLE);
	mParticleSystem->SetMaxParticleCount(MAX_PARTICLE);
//	mParticleSystem->SetDestructionByAge(true);

	const float32 faucetLength = mParticleSystem->GetRadius() * 2.0f;
	mEmitter.SetParticleSystem(mParticleSystem);
//	mEmitter.SetCallback(&mLifetimeRandomizer);
	mEmitter.SetPosition(b2Vec2(mPosition.x/PTM_RATIO, mPosition.y/PTM_RATIO));
	mEmitter.SetVelocity(b2Vec2(50.0f, 0.0f));
	mEmitter.SetSize(b2Vec2(0.0f, faucetLength));
	mEmitter.SetColor(b2ParticleColor(255, 255, 255, 255));
	mEmitter.SetEmitRate(100.0f);
	mEmitter.SetParticleFlags(b2_waterParticle | b2_particleContactFilterParticle);
}

void Flow::update(float dt) {
   mEmitter.Step(dt,NULL,0);
}

void Flow::createWall() {
	auto s = Director::getInstance()->getWinSize();
	// Define ground body
	b2BodyDef groundBodyDef;
	groundBodyDef.position.Set(0,0);	// bottom-left corner
	b2Body* groundBody = mWorld->CreateBody(&groundBodyDef);

	// Define ground shape
	{
		b2EdgeShape groundBox;
		b2FixtureDef groundFixtureDef;
		groundFixtureDef.shape = &groundBox;
		groundFixtureDef.restitution = 1.0f;
		groundFixtureDef.friction = 0.4f;
		
		// bottom
		groundBox.Set(b2Vec2(0/PTM_RATIO, 0/PTM_RATIO), 
					  b2Vec2(s.width/PTM_RATIO, 0/PTM_RATIO));
		groundBody->CreateFixture(&groundFixtureDef);
	}
	// top
	{
		b2EdgeShape groundBox;
		b2FixtureDef groundFixtureDef;
		groundFixtureDef.shape = &groundBox;
		groundFixtureDef.restitution = 1.0f;
		groundFixtureDef.friction = 0.4f;
			
		// top
		groundBox.Set(b2Vec2(0/PTM_RATIO, s.height/PTM_RATIO), 
				  b2Vec2(s.width/PTM_RATIO, s.height/PTM_RATIO));
		groundBody->CreateFixture(&groundFixtureDef);
	}
		
	// left
	{
		b2EdgeShape groundBox;
		b2FixtureDef groundFixtureDef;
		groundFixtureDef.shape = &groundBox;
		groundFixtureDef.restitution = 1.0f;
		groundFixtureDef.friction = 0.4f;
		
		// left
		groundBox.Set(b2Vec2(0/PTM_RATIO, 0/PTM_RATIO), 
				  b2Vec2(0/PTM_RATIO, s.height/PTM_RATIO));
		groundBody->CreateFixture(&groundFixtureDef);
	}
		
	// right
	{
		b2EdgeShape groundBox;
		b2FixtureDef groundFixtureDef;
		groundFixtureDef.shape = &groundBox;
		groundFixtureDef.restitution = 1.0f;
		groundFixtureDef.friction = 0.4f;
		
		// right
		groundBox.Set(b2Vec2(s.width/PTM_RATIO, 0/PTM_RATIO), 
				  b2Vec2(s.width/PTM_RATIO, s.height/PTM_RATIO));
		groundBody->CreateFixture(&groundFixtureDef);
	}
}

